const DELAYED_PROPERTY_DECORATORS = Symbol('delayedPropertyDecorators');

interface ElementParamsType {
    tag: string;
    style?: any;
}

interface MethodParamsType {
    type: string;
    tag: string;
}

interface eventMethodsMetadataType {
    eventSource: string;
    eventType: string;
    eventHandler: string;
}

interface watchPropMetadataType {
    sourceProp: string;
    PropHandler: (newValue: any, oldValue: any) => void;
}

export function Element(params: ElementParamsType) {
    return function <T extends { new(...args: any[]): HTMLElement }>(constructor: T) {
        class DerivativeElement extends constructor {

            trackedProperties: string[] = Reflect.getMetadata('properties', constructor) || [];
            eventMethodsList: eventMethodsMetadataType[] = Reflect.getMetadata('eventMethodsList', constructor) || [];
            watchPropList: watchPropMetadataType[] = Reflect.getMetadata('watchProp', constructor) || [];
            static observedAttributes: string[] = []
            shadow: ShadowRoot = this.attachShadow({ mode: "open" });
            render: () => string;

            constructor(...args: any[]) {
                super(...args);
                this.init();
            }

            init() {
                DerivativeElement.observedAttributes = this.trackedProperties;
                this.trackedProperties.forEach(item => {
                    if (this.getAttribute(item) != null) {
                        this[item] = this.getAttribute(item)
                    }
                })
                params.style ? this.addStyleFile() : ''
                this.DOMParser()
                this.bindEvents()
                this.bindWatchPropHandler()
            }

            DOMParser() {
                const template = this.render();
                const doc = new DOMParser().parseFromString(template, 'text/html');
                let children = Array.from(doc.querySelector("body").children);
                children.forEach(v => this.shadow.appendChild(v));
            }

            async addStyleFile() {
                const styleEl = document.createElement("style");
                styleEl.textContent = params.style;
                this.shadow.appendChild(styleEl);
            }

            bindEvents() {
                this.eventMethodsList.forEach(({ eventSource, eventType, eventHandler }) => {
                    const elements = this.shadow.querySelectorAll(eventSource);
                    elements.forEach((element: HTMLElement) => element.addEventListener(eventType, this[eventHandler].bind(this)));
                })
            }

            bindWatchPropHandler() {
                this.watchPropList.forEach(({ sourceProp, PropHandler }) => {
                    let oldValue = this[sourceProp];
                    Object.defineProperty(this, sourceProp, {
                        set: (newValue: any) => {
                            if (oldValue !== newValue) {
                                const oldVal = oldValue;
                                oldValue = newValue;
                                PropHandler.call(this, newValue, oldVal);
                            }
                        },
                        get: () => oldValue
                    });
                })
            }

            // 自定义元素添加至页面
            connectedCallback() {
                console.log("DerivativeElement connectedCallback");
            }


            // 自定义元素从页面中移除
            // 需要删除绑定的点击事件
            // disconnectedCallback() {
            //     console.log("disconnectedCallback");
            // }
        }
        // 注册WebComponents自定义组件
        customElements.define(params.tag, DerivativeElement);
    };
}


export function Props(target: any, propertyKey: string) {
    const constructor = target.constructor;
    const trackedProperties: string[] = Reflect.getMetadata('properties', constructor) || [];
    if (!trackedProperties.includes(propertyKey)) {
        trackedProperties.push(propertyKey);
    }
    Reflect.defineMetadata('properties', trackedProperties, constructor);
}

export function Method(params: MethodParamsType) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const constructor = target.constructor;
        const eventMethodsList: eventMethodsMetadataType[] = Reflect.getMetadata('eventMethodsList', constructor) || [];
        if (!eventMethodsList.some(item => item.eventHandler === propertyKey)) {
            eventMethodsList.push({
                eventSource: params.tag,
                eventType: params.type,
                eventHandler: propertyKey
            });
        }
        Reflect.defineMetadata('eventMethodsList', eventMethodsList, constructor);
    };
}

export function Watch(propName: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const constructor = target.constructor;
        const watchPropList: watchPropMetadataType[] = Reflect.getMetadata('watchProp', constructor) || [];
        if (!watchPropList.some(item => item.sourceProp === propertyKey)) {
            watchPropList.push({
                sourceProp: propName,
                PropHandler: descriptor.value,
            });
        }
        Reflect.defineMetadata('watchProp', watchPropList, constructor);

    };
}